package csm.common;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.sql.SQLException;

import org.xml.sax.SAXException;

/**
 * 전용으로 사용하는 일반적인 공용 오류 클래스입니다.
 *
 * @author seungmun
 * @since 2006.1.18.
 */
public class SMException extends Exception
{
    private static final long serialVersionUID = 1L;

    private Throwable         cause;

    /**
     * 기본 생성자
     */
    public SMException()
    {
        super("Error occurred in CSM Library components.");
    }

    /**
     * 기본 생성자
     *
     * @param message
     */
    public SMException(String message)
    {
        super(message);
    }

    /**
     * 기본 생성자
     *
     * @param message
     * @param cause
     *            {@link java.lang.Throwable}를 상속받은 객체로, 원인을 제공하는 예외
     */
    public SMException(String message, Throwable cause)
    {
        super(message);
        this.cause = cause;
    }

    public Throwable initCause(Throwable cause)
    {
        this.cause = cause;
        return this;
    }

    public Throwable getCause()
    {
        return cause;
    }

    public String getMessage()
    {
        String msg = super.getMessage();

        Throwable parent = this;
        Throwable child;

        while ((child = getNestedException(parent)) != null)
        {
            String msg2 = child.getMessage();

            if (msg2 != null)
            {
                if (msg != null)
                {
                    msg += ": " + msg2;
                }
                else
                {
                    msg = msg2;
                }
            }

            if (child instanceof SMException)
            {
                break;
            }
            parent = child;
        }
        return msg;
    }

    public void printStackTrace()
    {
        super.printStackTrace();

        Throwable parent = this;
        Throwable child;

        while ((child = getNestedException(parent)) != null)
        {
            System.err.print("Caused by: ");
            child.printStackTrace();
            if (child instanceof SMException)
            {
                break;
            }
            parent = child;
        }
    }

    public void printStackTrace(PrintStream s)
    {
        super.printStackTrace(s);

        Throwable parent = this;
        Throwable child;

        while ((child = getNestedException(parent)) != null)
        {
            s.print("Caused by: ");
            child.printStackTrace(s);
            if (child instanceof SMException)
            {
                break;
            }
            parent = child;
        }
    }

    public void printStackTrace(PrintWriter w)
    {
        super.printStackTrace(w);

        Throwable parent = this;
        Throwable child;

        while ((child = getNestedException(parent)) != null)
        {
            w.print("Caused by: ");
            child.printStackTrace(w);
            if (child instanceof SMException)
            {
                break;
            }
            parent = child;
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static Throwable getNestedException(Throwable parent, String className, String methodName)
    {
        try
        {
            Class testClass = Class.forName(className);
            Class objectClass = parent.getClass();
            if (testClass.isAssignableFrom(objectClass))
            {
                Class[] argClasses = new Class[0];
                Method method = testClass.getMethod(methodName, argClasses);
                Object[] args = new Object[0];
                return (Throwable) method.invoke(parent, args);
            }
        }
        catch (Exception ex)
        {
        }

        return null;
    }

    private static Throwable getNestedException(Throwable parent)
    {
        if (parent instanceof SMException)
        {
            return ((SMException) parent).getCause();
        }

        if (parent instanceof SAXException)
        {
            return ((SAXException) parent).getException();
        }

        if (parent instanceof SQLException)
        {
            return ((SQLException) parent).getNextException();
        }

        if (parent instanceof InvocationTargetException)
        {
            return ((InvocationTargetException) parent).getTargetException();
        }

        if (parent instanceof ExceptionInInitializerError)
        {
            return ((ExceptionInInitializerError) parent).getException();
        }

        if (parent instanceof RemoteException)
        {
            return ((RemoteException) parent).detail;
        }

        Throwable nestedException = getNestedException(parent, "javax.naming.NamingException", "getRootCause");
        if (nestedException != null)
        {
            return nestedException;
        }

        nestedException = getNestedException(parent, "javax.servlet.ServletException", "getRootCause");
        if (nestedException != null)
        {
            return nestedException;
        }

        return null;
    }

    public static String PrintOutException(Throwable e)
    {
        if (e == null)
            return "";

        StringBuffer sb = new StringBuffer();
        sb.append("Message: ").append(e.getMessage()).append("\r\n");
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        sb.append("Stack Trace: ").append(sw.toString()).append("\r\n");
        return sb.toString();
    }
}
